﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using Csu.Modsim.ModsimModel;
using ASquared.ModelOptimization;
using ASquared.SymbolicMath;
using ASquared;

namespace iwpm.optimization
{
    public class HydroTargetSeeker : OptiFunctionBase, IConditionalGradientSolvableFunction, IHydroDebuggable
    {
        public static bool DoMask = true; 

        // Instance variables 
        private Model _model;
        private double _weight;
        private HydroTargetCalculator _hydroCalc;
        private HydropowerTarget[] _targets;
        public event EventHandler<MessageEventArgs> WritingDebug;
        public bool AddRouting = true; 

        // Properties 
        /// <summary>Gets and sets the weight associated with the hydropower targets.</summary>
        public double Weight
        {
            get
            {
                return _weight;
            }
            set
            {
                _weight = value;
            }
        }
        /// <summary>Gets the array hydropower targets associated with this instance.</summary>
        public HydropowerTarget[] Targets
        {
            get
            {
                return _targets;
            }
            set
            {
                _targets = value;
                _hydroCalc = new HydroTargetCalculator(_model, _targets);
            }
        }
        /// <summary>Gets the hydropower target penalty function.</summary>
        public Symbol HydroPenaltyFunction
        {
            get
            {
                return _weight * _hydroCalc.TotalSqrEnergyDiff();
            }
        }
        /// <summary>Gets the symbolic representation of the objective function used to meet a specified power target.</summary>
        public override Symbol fxn
        {
            get
            {
                if (_model.hydro.IterativeTechnique != IterativeSolutionTechnique.Nothing && _model.hydro.IterativeTechnique != IterativeSolutionTechnique.SeparateHydroLinks)
                {
                    Symbol hydroStuff = 0;
                    foreach (HydropowerUnit unit in _model.hydro.HydroUnits)
                        foreach (Link link in unit.FlowLinks)
                        {
                            // penalize deviation from upper bound on hydro control link
                            Link hc = link.mlInfo.hydroControl;
                            hydroStuff += _weight * ((base.SymbolVariables[hc.number - 1] - hc.mlInfo.hi / _model.ScaleFactor) ^ 2);
                            // penalize deviation from zero on additional hydro flow link
                            Link ha = link.mlInfo.hydroAdditional;
                            hydroStuff += _weight * (base.SymbolVariables[ha.number - 1] ^ 2);
                        }
                    return hydroStuff;
                }
                else
                {
                    Symbol hydroStuff = this.HydroPenaltyFunction;
                    Symbol theFxn = base.GetModsimObjectiveFunction(hydroStuff.Variables().ToArray()) + hydroStuff;
                    Symbol routing = AddRouting ? _weight * 100.0 * base.RoutingCompatibility : 0.0; 
                    this.WriteDebugFile(theFxn, hydroStuff.Variables().ToArray());
                    return hydroStuff + routing + base.SpillPenaltyFunction; // theFxn;
                }
            }
        }
        /// <summary>Gets the hydropower target calculator.</summary>
        public HydroTargetCalculator TargetCalculator
        {
            get
            {
                return _hydroCalc;
            }
            set
            {
                _hydroCalc = value; 
            }
        }

        // Constructors 
        /// <summary>Constructs and instance of a hydropower target meeter.</summary>
        /// <param name="model">The model for which to match hydropower targets.</param>
        /// <param name="weight">The weight assigned to the penalty function portion of the hydropower targets.</param>
        public HydroTargetSeeker(Model model, double weight)
            : base(model)
        {
            _model = model;
            _targets = model.hydro.HydroTargets;
            _hydroCalc = new HydroTargetCalculator(model, _targets);
            _weight = weight;
        }

        // Methods
        private void WriteDebugFile(Symbol fullfxn, string[] variables)
        {
            EventHandler<MessageEventArgs> eHand = WritingDebug;
            if (eHand != null)
            {
                HydroFunctionDebuggerEventArgs e
                    = new HydroFunctionDebuggerEventArgs(_model, fullfxn, variables);
                eHand(this, e);
            }
        }

        private void SetupMask()
        {
            double[] mask = new double[_model.Links_All.Length];
            if (DoMask)
                foreach (HydropowerTarget target in _targets)
                    foreach (Link link in target.FlowLinks)
                        if (_model.hydro.IterativeTechnique == IterativeSolutionTechnique.Nothing)
                            mask[link.number - 1] = 1;
                        else
                        {
                            // penalize deviation from upper bound on hydro control link
                            mask[link.mlInfo.hydroControl.number - 1] = 1;
                            mask[link.mlInfo.hydroAdditional.number - 1] = 1;
                        }
            else
                foreach (Link link in _model.Links_All)
                    mask[link.number - 1] = 1;
            base.variableMask = mask;
        }

        public override void Initialize()
        {
            // initiallize 
            base.Initialize();
            _hydroCalc.Initialize();

            // prepare the hydropower controller by setting costs of hydro links to 1
            //  and that the hydropower controller is active
            HydroPreparer hPrep = new HydroPreparer(_model, _targets, DefineConstants.COST_LARGE);
            hPrep.Prepare();

            this.SetupMask();
            base.RoutingAlphaParameter = _weight * 100.0;
            base.SpillWeight = _weight * 100.0; 
        }


    }
}
